﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Diagnostics;
using SimpleIR;

namespace InterpreterCore
{
	/// <summary>
	/// This class represents the eval stack in a given call frame.
	/// </summary>
	internal sealed class EvalStack
	{
		/// <summary>
		/// List of all entries on the eval stack, head of stack is the last element in the list.
		/// </summary>
		private readonly List<EvalStackEntry> m_evalstack;

		internal EvalStack()
		{ this.m_evalstack = new List<EvalStackEntry>(); }

		public override string ToString()
		{
			StringBuilder sb = new StringBuilder("Eval=[");
			for(int i = 0; i < this.m_evalstack.Count; ++i)
			{
				if(i != 0)
					sb.Append(", ");
				sb.Append(this.m_evalstack[i].ToString());
			}
			sb.Append("]");

			return sb.ToString();
		}

		internal string DebuggerString()
		{
			StringBuilder sb = new StringBuilder("Eval=[");
			for(int i = 0; i < this.m_evalstack.Count; ++i)
			{
				if(i != 0)
					sb.Append(", ");
				sb.Append(this.m_evalstack[i].DebuggerString());
			}
			sb.Append("]");

			return sb.ToString();
		}

		internal string EmitForTrace()
		{
			StringBuilder sb = new StringBuilder("Eval=[");
			for(int i = 0; i < this.m_evalstack.Count; ++i)
			{
				if(i != 0)
					sb.Append(",");
				sb.Append(this.m_evalstack[i].EmitForTrace());
			}
			sb.Append("]");

			return sb.ToString();
		}

		internal void SerializeForHeapDump(System.IO.TextWriter dumpstr)
		{
			dumpstr.Write("Eval=[");
			for(int i = 0; i < this.m_evalstack.Count; ++i)
			{
				if(i != 0)
					dumpstr.Write(",");
				this.m_evalstack[i].SerializeForHeapDump(dumpstr);
			}
			dumpstr.Write("]");
		}

		internal void SerializeForDGMLDisplay(System.Xml.XmlWriter nodexw, System.Xml.XmlWriter edgexw, string mprfx)
		{
			for(int i = 0; i < this.m_evalstack.Count; ++i)
				this.m_evalstack[i].SerializeForDGMLDisplay(nodexw, edgexw, String.Format("{0}eval[{1}]", mprfx, i));
		}

		internal static EvalStack ParseEvalStack(Dictionary<string, string> pkvp)
		{
			string ees = pkvp["Eval"];
			string[] evala = ees.Substring(1, ees.Length - 2).Split(new char[] { ',' });

			EvalStack res = new EvalStack();
			for(int i = 0; i < evala.Length; ++i)
			{
				EvalStackEntry evse = EvalStackEntry.ParseValue(evala[i]);
				res.m_evalstack.Add(evse);
			}

			return res;
		}

		internal IRType PeekType()
		{
			Debug.Assert(this.m_evalstack.Count != 0);

			return this.m_evalstack[this.m_evalstack.Count - 1].EntryType;
		}

		/// <summary>
		/// Peek at the type of the kth element in the stack (location 0 is the top).
		/// </summary>
		internal IRType PeekTypeK(int pos)
		{
			Debug.Assert(this.m_evalstack.Count > pos);

			return this.m_evalstack[this.m_evalstack.Count - (pos + 1)].EntryType;
		}

		[Conditional("DEBUG")]
		internal void AssertTopTwoEntriesAreSameType()
		{
			Debug.Assert(this.m_evalstack.Count >= 2);
			
			int toploc = this.m_evalstack.Count - 1;
			Debug.Assert(this.m_evalstack[toploc - 1].EntryType == this.m_evalstack[toploc].EntryType);
		}

		private void Push(EvalStackEntry evse)
		{
			this.m_evalstack.Add(evse);
		}

		private EvalStackEntry Pop()
		{
			Debug.Assert(this.m_evalstack.Count != 0);

			EvalStackEntry eve = this.m_evalstack[this.m_evalstack.Count - 1];
			this.m_evalstack.RemoveAt(this.m_evalstack.Count - 1);

			return eve;
		}

		internal void PushRawStackNumericVal(StackNumericValue nv, IREvalStackPrimitiveType espt)
		{
			EvalStackEntry evsdp = EvalStackEntry.CreateRawStackNumeric(nv, espt);

			this.Push(evsdp);
		}

		internal void PushNumericEvalEntry(NumericValue nv)
		{
			IREvalStackPrimitiveType espt = (IREvalStackPrimitiveType)GlobalInterpreterState.TProg.ConvertTypeToStdStackRep(nv.ValType);
			EvalStackEntry evsdp = EvalStackEntry.CreateNumericEvalEntry(nv, espt);

			this.Push(evsdp);
		}

		internal void PushPointerEvalEntry(PtrValue pv)
		{
			EvalStackEntry evsdp = EvalStackEntry.CreatePointerEvalEntry(pv);

			this.Push(evsdp); 
		}

		internal void PushReferenceEvalEntry(ReferenceValue rv)
		{
			EvalStackEntry evsdp = EvalStackEntry.CreateReferenceEvalEntry(rv);

			this.Push(evsdp); 
		}

		internal void PushStructEvalEntry(IRStructType st, CompositeValueEntry[] cv)
		{
			EvalStackEntry evsdp = EvalStackEntry.CreateStructEvalEntry(cv, st);

			this.Push(evsdp); 
		}

		internal void PushFunctionPtrEvalEntry(IRFunctionPointerType fpt, IRInvokeIdentity iiv)
		{
			EvalStackEntry evsdp = EvalStackEntry.CreateFunctionPtrEvalEntry(iiv, fpt);

			this.Push(evsdp); 
		}

		internal IEnumerable<IEnumerable<int>> EnumerateTrgtAddresses()
		{
			for(int i = this.m_evalstack.Count - 1; i >= 0; --i)
				yield return this.m_evalstack[i].EnumerateTrgtAddresses(this.m_evalstack[i].EntryType);
		}

		internal void DiscardPop()
		{
			this.m_evalstack.RemoveAt(this.m_evalstack.Count - 1);
		}

		internal void DiscardRcvrPosition(int pos)
		{
			this.m_evalstack.RemoveAt(this.m_evalstack.Count - (pos + 1)); 
		}

		internal void Dup()
		{
			Debug.Assert(this.m_evalstack.Count != 0);

			EvalStackEntry dupe = this.m_evalstack[this.m_evalstack.Count - 1];
			if(!(dupe.EntryType is IRStructType))
				this.m_evalstack.Add(dupe);
			else
			{
				CompositeValueEntry[] copycve = (CompositeValueEntry[])dupe.ReadStruct().Clone();
				EvalStackEntry evsdp = EvalStackEntry.CreateStructEvalEntry(copycve, (IRStructType)dupe.EntryType);
				this.m_evalstack.Add(evsdp);
			}
		}

		/// <summary>
		/// Duplicate the element at the given position onto the top of the stack.
		/// </summary>
		internal void DupPositionToTop(int k)
		{
			Debug.Assert(this.m_evalstack.Count > k);

			EvalStackEntry dupe = this.m_evalstack[this.m_evalstack.Count - (k + 1)];
			if(!(dupe.EntryType is IRStructType))
				this.m_evalstack.Add(dupe);
			else
			{
				CompositeValueEntry[] copycve = (CompositeValueEntry[])dupe.ReadStruct().Clone();
				EvalStackEntry evsdp = EvalStackEntry.CreateStructEvalEntry(copycve, (IRStructType)dupe.EntryType);
				this.m_evalstack.Add(evsdp);
			}
		}

		/// <summary>
		/// Rotate the top element into the given position (relative to after the element is popped).
		/// </summary>
		internal void RotateTopToPosition(int k)
		{
			Debug.Assert(this.m_evalstack.Count > k + 1);

			EvalStackEntry dupe = this.Pop();
			this.m_evalstack[this.m_evalstack.Count - (k + 1)] = dupe;
		}

		/// <summary>
		/// Rotate the top element into the given reciever position.
		/// </summary>
		internal void RotateTopToReciever(int k)
		{
			Debug.Assert(this.m_evalstack.Count >= k);

			EvalStackEntry dupe = this.Pop();
			this.m_evalstack.Insert(this.m_evalstack.Count - (k - 1), dupe);
		}

		/// <summary>
		/// Set the type of an eval stack entry at method call entry.
		/// </summary>
		internal void SetArgType(IRType tt, int k)
		{
			EvalStackEntry evse = this.m_evalstack[this.m_evalstack.Count - k];
			this.m_evalstack[this.m_evalstack.Count - k] = evse.ConvertToType(tt);
		}

		/// <summary>
		/// Move the top <c>argount</c> elements from <c>callerstack</c> into this evalstack and then clear them from <c>callerstack</c>
		/// </summary>
		internal void MoveCallArgsToThis(EvalStack callerstack, int argcount)
		{
			Debug.Assert(this.m_evalstack.Count == 0);

			if(argcount == 0)
				return;

			for(int i = 0; i < argcount; ++i)
			{
				EvalStackEntry cpevse = callerstack.m_evalstack[callerstack.m_evalstack.Count - argcount + i];
				this.m_evalstack.Add(cpevse);
			}

			callerstack.m_evalstack.RemoveRange(callerstack.m_evalstack.Count - argcount, argcount);
		}

		/// <summary>
		/// Move the return value from this to the callerstack.
		/// </summary>
		internal void MoveReturnValue(EvalStack callerstack, IRType returntype)
		{
			Debug.Assert(this.m_evalstack.Count == 1);

			if(returntype is IRPrimitiveNumericType || returntype is IREnumType)
			{
				IRPrimitiveNumericType pnt = (returntype is IREnumType) ? ((IREnumType)returntype).UnerlyingNumericType : ((IRPrimitiveNumericType)returntype);
				NumericValue nv = this.PopNumericValueAsType(pnt);
				callerstack.PushNumericEvalEntry(nv);
			}
			else if(returntype is IRPtrType)
			{
				PtrValue pv = this.PopPointerAsType((IRPtrType)returntype);
				callerstack.PushPointerEvalEntry(pv);
			}
			else
			{
				CompositeValueEntry[] cve = this.PopStruct();
				callerstack.PushStructEvalEntry((IRStructType)returntype, cve);
			}
		}

		/////////////////////////////////////////////////
		//These methods are used for extracting a value when popping it off the eval stack

		internal StackNumericValue PopRawEvalStackNumeric()
		{
			EvalStackEntry evse = this.Pop();
			return evse.ReadRawStackNumeric();
		}

		internal NumericValue PopNumericValueAsType(IRType typetopop)
		{
			if(typetopop is IREnumType)
				typetopop = ((IREnumType)typetopop).UnerlyingNumericType;

			EvalStackEntry evse = this.Pop();
			return evse.ReadNumericValueAsType((IRPrimitiveNumericType)typetopop);
		}

		internal PtrValue PopPointerAsType(IRPtrType tt)
		{
			EvalStackEntry evse = this.Pop();
			return evse.ReadPointerAsType(tt);
		}

		internal PtrValue PopPointerNoTypeConvert()
		{
			EvalStackEntry evse = this.Pop();
			return evse.ReadPointerNoTypeConvert();
		}

		internal PtrValue PeekPtrK(int k)
		{
			Debug.Assert(this.m_evalstack.Count > k);
			Debug.Assert(this.m_evalstack[this.m_evalstack.Count - (k + 1)].EntryType is IRPtrType);

			return this.m_evalstack[this.m_evalstack.Count - (k + 1)].ReadPointerNoTypeConvert();
		}

		internal ReferenceValue PopReference()
		{
			EvalStackEntry evse = this.Pop();
			return evse.ReadReference();
		}

		internal ReferenceValue PeekReferenceK(int k)
		{
			Debug.Assert(this.m_evalstack.Count > k);
			Debug.Assert(this.m_evalstack[this.m_evalstack.Count - (k + 1)].EntryType is IRReferenceType);

			return this.m_evalstack[this.m_evalstack.Count - (k + 1)].ReadReference();
		}

		internal CompositeValueEntry[] PopStruct()
		{
			EvalStackEntry evse = this.Pop();

			Debug.Assert(evse.EntryType is IRStructType);
			return evse.ReadStruct();
		}

		internal IRInvokeIdentity PopFunctionPtr()
		{
			EvalStackEntry evse = this.Pop();

			Debug.Assert(evse.EntryType is IRFunctionPointerType);
			return evse.ReadFunctionPtr();
		}
	}
}
